#include "myproxy.h"
#include <sys/stat.h>

void find_request_relative_path_and_port(struct struct_request_header *requestHeader) {
	char *req_host_temp = calloc(1, MAX_ADDR_SIZE);
	char *req_host_temp_saveptr;
	char *relative_path;
	char *relative_path_in_place;
	char port[6];
	port[5] = '\0';

	if (requestHeader->relative_path[0] == 'h') {
		/* Case 1: req_relative_path is NOT relative_path */
		requestHeader->relative_path[5] = 'X';
		requestHeader->relative_path[6] = 'X';

		// the path without "/"
		if (strchr(requestHeader->relative_path, (int) '/') == NULL) {
			strcpy(requestHeader->relative_path, "/\0");
		}
		else {
			relative_path_in_place = strchr(requestHeader->relative_path, (int) '/');
			relative_path = calloc(1, strlen(relative_path_in_place) + 1);
			relative_path = strcpy(relative_path, relative_path_in_place);
			memset(requestHeader->relative_path, '\0', strlen(requestHeader->relative_path));
			strcpy(requestHeader->relative_path, relative_path);
			free(relative_path);
		}
	}
	else {
		/* Case 2: req_relative_path is relative_path */
		/* Good, do nothing... */
	}
	
	/* FIND THE WEB SERVER PORT */
	req_host_temp = strncpy(req_host_temp, requestHeader->host, MAX_ADDR_SIZE);
	if (strrchr(req_host_temp, (int)':') != NULL) {
		/* NOT DEFAULT PORT 80 */
		char* host_token = strtok_r(req_host_temp, ":", &req_host_temp_saveptr);
		char* port_token = strtok_r(NULL, ":", &req_host_temp_saveptr);
		strncpy(requestHeader->host, host_token, MAX_ADDR_SIZE);
		strncpy(port, port_token, 5);
		
		if ((port[0] >= '0') && (port[0] <= '9'))
			sscanf(port, "%hd", &requestHeader->port);
	}
	else{
		requestHeader->port = 80;
	}
	free(req_host_temp);
}

void find_request_file_type(struct struct_request_header *requestHeader) {
	int i, j=0;
	char buffer[4];
	memset(buffer, '\0', 4);
	
	for (i=strlen(requestHeader->relative_path) ; i>0 ; i--) {
		if ((requestHeader->relative_path[i-1] == '.') || (j >= 4)) {
			break;
		}
		else {
			buffer[j] = requestHeader->relative_path[i-1];
			j++;
		}
	}
	
	if ((strncmp(buffer, "lmth", 4) == 0) || (strncmp(buffer, "gpj", 4) == 0) ||		// html, jpg, gif, txt
		(strncmp(buffer, "fig", 4) == 0) || (strncmp(buffer, "txt", 4) == 0))
		requestHeader->is_interested_type = TRUE;
	else
		requestHeader->is_interested_type = FALSE;
}

char* parse_request_header(char* req_header_raw, struct struct_request_header *requestHeader) {
	char* header_line;
	char* current_token;
	char* peek_token;
	char* header_line_saveptr, *token_saveptr;
	char* buffer;

	requestHeader->is_get = FALSE;
	requestHeader->is_ims = FALSE;
	requestHeader->is_cache_control = FALSE;
	requestHeader->is_keep_alive = TRUE;
	//printf("Request header: \n%s\n", req_header_raw);
	header_line = strtok_r(req_header_raw, "\r\n", &header_line_saveptr);
	
	while(header_line != NULL) {
		//Can current token be null sometimes???
		current_token = strtok_r(header_line, " ", &token_saveptr);

		while(current_token != NULL) {
			//printf("\033[1;32mcurrent_token -> %s, peek -> %s\n\033[0m", current_token, peek_token);
			if (strcmp(current_token, "GET") == 0) {
				peek_token = strtok_r(NULL, " ", &token_saveptr);
				requestHeader->is_get = TRUE;
				strcpy(requestHeader->relative_path, peek_token);
			}
			
			if (strcmp(current_token, "POST") == 0) {
				peek_token = strtok_r(NULL, " ", &token_saveptr);
				strcpy(requestHeader->relative_path, peek_token);
			}

			if ((strcmp(current_token, requestHeader->relative_path) == 0)) {
				
				if(peek_token == NULL)
					break;
				strcpy(requestHeader->connection_type, peek_token);
				peek_token = strtok_r(NULL, " ", &token_saveptr);
			}

			if (strcmp(current_token, "Host:") == 0) {
				peek_token = strtok_r(NULL, " ", &token_saveptr);	
				strcpy(requestHeader->host, peek_token);
			}

			if (strcmp(current_token, "If-Modified-Since:") == 0) {
				requestHeader->is_ims = TRUE;
				//peek_token = strtok_r(NULL, " ", &token_saveptr);
				peek_token = strtok_r(NULL, "\0", &token_saveptr);	
				strcpy(requestHeader->ims_time_string, peek_token);
				requestHeader->ims_time = to_time_t(peek_token);
			}
			
			// not tested
			if (strcmp(current_token, "Connection:") == 0) {
				peek_token = strtok_r(NULL, "\0", &token_saveptr);	
			//printf("\033[1;32mcurrent_token -> %s, peek -> %s\033[0m\n\n", current_token, peek_token);
				if(strcmp(peek_token, "close") == 0){
					//Close connection
					requestHeader->is_keep_alive = FALSE;
					//printf("\033[1;31mConnection close requested by client!\033[0m\n");
				}else{
					requestHeader->is_keep_alive = TRUE;
				}
			}
			
			// not tested
			if (strcmp(current_token, "Proxy-Connection:") == 0) {
				peek_token = strtok_r(NULL, "\0", &token_saveptr);	
				//printf("\033[1;32mcurrent_token -> %s, peek -> %s\033[0m\n\n", current_token, peek_token);
				if (strcmp(peek_token, "keep-alive") == 0){
					requestHeader->is_keep_alive = TRUE;
				}else{
					requestHeader->is_keep_alive = FALSE;
					//printf("\033[1;31mConnection close requested by client!\033[0m\n");
				}
			}
			
			
			if ((strcmp(current_token, "Cache-Control:") == 0)) {
			//if ((strcmp(current_token, "no-cache") == 0) || (strcmp(current_token, "no-cache,") == 0) || (strcmp(current_token, ",no-cache") == 0)) {
				peek_token = strtok_r(NULL, "\0", &token_saveptr);
				
				buffer = strtok_r(peek_token, " ,;\0", &token_saveptr);
				
			
				while (buffer != NULL) {
					if (strcmp(buffer, "no-cache") != 0) {
						buffer = strtok_r(NULL, " ,;\0", &token_saveptr);
					} else {
						//printf("no cache!!! %s   %s\n", peek_token, header_line);
						requestHeader->is_cache_control = TRUE;
						break;
					}
				}
			}

			current_token = peek_token;
			peek_token = strtok_r(NULL, " ", &token_saveptr);
		}
		header_line = strtok_r(NULL, "\r\n", &header_line_saveptr);
	}
	
	// Return the updated address for FREE
	return req_header_raw;
}

int process_request(char* req_header_raw, int req_header_size, int client_socket_descriptor) {
	struct struct_request_header requestHeader;
	struct struct_request_identity requestIdentity;
	char* req_header_raw_for_parsing = calloc(1, req_header_size);
	char has_response = FALSE;
	int server_socket_descriptor;
	int client_keep_alive;
	char require_keep_alive = 'X';

	memset(&requestHeader, '\0', sizeof(struct struct_request_header));
	memset(&requestIdentity, '\0', sizeof(struct struct_request_identity));

	memcpy(req_header_raw_for_parsing, req_header_raw, req_header_size);

	req_header_raw_for_parsing = parse_request_header(req_header_raw_for_parsing, &requestHeader);
	free(req_header_raw_for_parsing);

	find_request_relative_path_and_port(&requestHeader);
	find_request_file_type(&requestHeader);

	requestIdentity.client_socket_descriptor = client_socket_descriptor;
	requestIdentity.ims_time = requestHeader.ims_time;
	requestIdentity.is_ims = requestHeader.is_ims;
	requestIdentity.is_cache_control = requestHeader.is_cache_control;
	requestIdentity.is_interested_type = requestHeader.is_interested_type;
	strcpy(requestIdentity.ims_time_string, requestHeader.ims_time_string);
	strcpy(requestIdentity.connection_type, requestHeader.connection_type);

	//requestIdentity.file_name = generate_filename(requestHeader.host, requestHeader.relative_path, requestHeader.port);
	strcpy(requestIdentity.file_name, generate_filename(requestHeader.host, requestHeader.relative_path, requestHeader.port));
	
	// We only handle GET request
	if (requestHeader.is_get == FALSE) {
		fprintf(stderr, "\033[1;31mProxy> Error: Non-GET request received.\033[0m\n");
		return -1;
	}
	
	if (strcmp(requestHeader.relative_path, "") == 0) {
		fprintf(stderr, "\033[1;31mProxy> Error: Receive request for empty object.\033[0m\n");
		return -1;
	}
	
	// Request object is NOT CACHED
	if (is_cached(requestIdentity.file_name) == FALSE) {
		printf("\033[1;34mProxy> Request object path = <%s>.\033[0m\n", requestHeader.relative_path);
		printf("\033[1;34mProxy> Request object is not in cache.\033[0m\n");
		
		// according to spec. so forward exact request without changing header
		//server_socket_descriptor = forward_exact_request(client_socket_descriptor, req_header_raw, &requestHeader);
		
		// according to tutorial, should change header URL to relative path
		server_socket_descriptor = forward_request(client_socket_descriptor, req_header_raw, &requestHeader);
		has_response = TRUE;

	// Request object is CACHED && GET && INTERESTED
	} else {
		if ((requestHeader.is_get == TRUE) && (requestHeader.is_interested_type == TRUE)) {
			printf("Proxy> Request object path = <%s>.\n", requestHeader.relative_path);
			printf("Proxy> Request object is INTERESTED and IN CACHE, file name = <%s>\n", requestIdentity.file_name);

			// Case 1: If request do NOT contain IMS && do NOT contain cache-Control, return HTTP 200 response and cached data
			if ((requestIdentity.is_ims == FALSE) && (requestIdentity.is_cache_control == FALSE)) {
				printf("\033[1;34mProxy> Case 1 request: no IMS, no cache-Control -> return HTTP 200 and cached object.\033[0m\n");
				case1_2b_request_process(&requestIdentity);
				//close connection to server and client
				//close(requestIdentity.client_socket_descriptor); 
				//close(requestIdentity.server_socket_descriptor);

			} else if ((requestIdentity.is_ims == TRUE) && (requestIdentity.is_cache_control == FALSE)) {
				// Case 2a: Request IMS > Data last modified time, return HTTP 304 and close connection
				if (requestIdentity.ims_time > find_cache_last_modified_time(requestIdentity.file_name)) {
					printf("\033[1;34mProxy> Case 2a request: with ims, no cache-Control, ims>lmt, return HTTP 304.\033[0m\n");
					case2a_request_process(&requestIdentity);
					require_keep_alive = FALSE;
					//no server connection just close client
					//close(requestIdentity.client_socket_descriptor);
				}
				// Case 2b: Request IMS <= Data last modified time, return HTTP 200 and cached data
				else {
					printf("\033[1;34mProxy> Case 2b request: with ims, no cache-Control, ims<=lmt, return HTTP 200 and cached object.\033[0m\n");
					case1_2b_request_process(&requestIdentity);
					//no server connection just close client
					//close(requestIdentity.client_socket_descriptor);					
				}

			// Case 3: IMS = LMT, insert IMS header to request
			} else if ((requestIdentity.is_ims == FALSE) && (requestIdentity.is_cache_control == TRUE)) {
				printf("\033[1;34mProxy> Case 3 request: no ims, with cache-Control, IMS = LMT, insert IMS header to request.\033[0m\n");
				server_socket_descriptor = case3_request_process(client_socket_descriptor, req_header_raw, &requestHeader, &requestIdentity);
				has_response = TRUE;
				//keep connection open for response
			
			} else if ((requestIdentity.is_ims == TRUE) && (requestIdentity.is_cache_control == TRUE)) {
				// Case 4a: LMT > IMS, set IMS = LMT
				if (find_cache_last_modified_time(requestIdentity.file_name) > requestIdentity.ims_time) {
					printf("\033[1;34mProxy> Case 4a request: with ims & cache-Control, LMT > IMS, set IMS = LMT\033[0m\n");
					server_socket_descriptor = case4a_request_process(client_socket_descriptor, req_header_raw, &requestHeader, &requestIdentity);
				// Case 4b:
				} else {
					printf("\033[1;34mProxy> Case 4b request: with ims & cache-Control, LMT <= IMS, forward request.\033[0m\n");
					server_socket_descriptor = forward_request(client_socket_descriptor, req_header_raw, &requestHeader);
				}
				//keep connections open for responses
				has_response = TRUE;
			}
		} else {
			// Seems impossiable case... (cached uninterested object...?)
			printf("Proxy> Request object path = <%s>.\n", requestHeader.relative_path);
			printf("Proxy> Request object is NOT INTERESTED / NOT GET and IN CACHE, file name = <%s>\n", requestIdentity.file_name);
			server_socket_descriptor = forward_request(client_socket_descriptor, req_header_raw, &requestHeader);
			has_response = TRUE;
		}
	}
	
	// Cannot connect to required server
	if(server_socket_descriptor < 0){
		return -1;
	}

	// Save the server socket descriptor after contacting the server.
	requestIdentity.server_socket_descriptor = server_socket_descriptor;
	
	// Terminate the connection if connection/ proxy close is exist.
	if (requestHeader.is_keep_alive == TRUE) {
		client_keep_alive = 1;
	} else {
		client_keep_alive = 0;
	};

	// RECEIVE AND PROCESS THE RESPONSE FROM SERVER
	if (has_response == TRUE) {
		printf("Proxy> Going to receive server's response.\n");
		// Terminate the connectino if response is HTTP 304, no matter what the header said
		if (receive_and_process_response_thread(&requestIdentity) == 0) {
			client_keep_alive = 0;
		}
	}
	
	// Terminate the connectino if response is HTTP 304, no matter what the header said
	if (require_keep_alive == FALSE) {
		client_keep_alive = 0;
	}

	return client_keep_alive;
}


void receive_and_process_request_thread(int client_socket_descriptor) {
#ifdef __MULTI_THREAD__
		pthread_detach(pthread_self());
#endif
	char* req_header_raw = calloc(1, MAX_HEADER_SIZE);
	char buffer;
	int state;
	int req_header_size;
	int client_keep_alive;
	struct timeval time_out;
	fd_set all_fds, read_fds;

	/* Receive HTTP Header */
	while(1) {
		printf("Proxy> Waiting for request on socket %d.\n", client_socket_descriptor);
		req_header_size = 0;
		state = 0;
		memset(req_header_raw, '\0', MAX_HEADER_SIZE);

		FD_ZERO(&all_fds);
		FD_ZERO(&read_fds);
		FD_SET(client_socket_descriptor, &all_fds);
		memcpy(&read_fds, &all_fds, sizeof(fd_set));

		// Set the timeout to 15s
		time_out.tv_sec = HTTP_TIMEOUT;
		time_out.tv_usec = 0;
		if (select(client_socket_descriptor+1, &read_fds, NULL, NULL, &time_out) < 0) {
			if (errno == EBADF) {
				fprintf(stderr, "Proxy> Error: Cannot connection to socket %d.\n", client_socket_descriptor);
			} else if (errno == EINTR) {
				fprintf(stderr, "Proxy> Error: A signal was caught when selecting socket.\n");
			} else if (errno == EINVAL) {
				fprintf(stderr, "Proxy> Error: nfds is negative or the value contained within timeout is invalid.\n");
			} else if (errno == ENOMEM) {
				fprintf(stderr, "Proxy> Error: Unable to allocate memory for internal tables during select.\n");
			}
			free(req_header_raw);
			return;
		}

		if (FD_ISSET(client_socket_descriptor, &read_fds)) {
			do {
				if(recvn(client_socket_descriptor, &buffer, sizeof(char)) == sizeof(char)){
					req_header_size++;
				} else {
					printf("Error char: <%d>, \\r is %d \\n is %d\n", (int) buffer, (int)'\r', (int)'\n');
					fprintf(stderr, "Error while reading a request from client socket %d.\n", client_socket_descriptor);
					printf("Proxy> Receive and process request thread exits.\n");

					close(client_socket_descriptor);
					free(req_header_raw);
#ifdef __MULTI_THREAD__
					pthread_exit("");
#endif
					return;
				}

				if(req_header_size >= MAX_HEADER_SIZE){
					fprintf(stderr, "\033[1;31mProxy> Error: Too big request header, this should not be possible.\033[0m\n");
					free(req_header_raw);
					return;
					//exit(-1);
				}
				req_header_raw = strncat(req_header_raw, &buffer, 1);

				// Receive HTTP Header until \r\n\r\n
				if ((buffer == '\r') && ((state == 0) || (state == 2)))
					state++;
				else
					if ((buffer == '\n') && ((state == 1) || (state == 3)))
						state++;
					else
						state = 0;
			} while(state != 4);
			printf("Last request char: <%d>, \\r is %d \\n is %d\n", (int) buffer, (int)'\r', (int)'\n');
			
			//printf("\033[1;32m%s\033[0m\n", req_header_raw);
			client_keep_alive = process_request(req_header_raw, req_header_size, client_socket_descriptor);
			
			if (client_keep_alive == 0) {
				printf("Proxy> Client's connection %d closed.\n", client_socket_descriptor);
				break;
			} else if (client_keep_alive == -1) {
				printf("Proxy> Error: Cannot connect to HTTP server.\n");
				break;
			}
			
		} else {
			printf("Proxy> Timeout (%ds) while waiting a request on socket %d.\n", HTTP_TIMEOUT, client_socket_descriptor);
			//break;
		}
	}
	
	// Close the connection between client and proxy
	close(client_socket_descriptor);
	free(req_header_raw);
	printf("Proxy> Receive and process request thread exits.\n");
#ifdef __MULTI_THREAD__
	pthread_exit("");
#endif
}
